
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "dlist.h"

#define log(_fmt_, ...)         printf("[%s:%d][%s] "_fmt_"\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define min(a, b)               (a < b ? a : b)

/**
 * PPOOL_SIZE 0x3000 CLUSTER_MGR_SIZE 0x8 CLUSTER_DATA_SIZE 0x4 CLUSTER_NUM 0x400 CLUSTER_MGR_TOTAL_SIZE 0x2000 CLUSTER_DATA_TOTAL_SIZE 0x1000
 * */
#define POOL_SIZE               (32 * 1024)
#define CLUSTER_MGR_SIZE        (sizeof(struct ramfs_mgr))
#define CLUSTER_DATA_SIZE       (32)
#define CLUSTER_NUM             (POOL_SIZE / (CLUSTER_MGR_SIZE + CLUSTER_DATA_SIZE)) // should not larger than MAX(15bit)
#define CLUSTER_MGR_TOTAL_SIZE  (CLUSTER_NUM * CLUSTER_MGR_SIZE)
#define CLUSTER_DATA_TOTAL_SIZE (CLUSTER_NUM * CLUSTER_DATA_SIZE)
#define TAG_BIT                 (0x0080)
#define TAG_HASH_SIZE           16
#define TAG_HASH_MASK           0x0f

struct ramfs_mgr {
    union {
        unsigned short first_data;
        unsigned short next_data;
    };
    // unsigned char ecc[4];
};

struct ramfs_tag {
    sys_dnode_t node;
    unsigned short tag;
    unsigned short sz;
};

sys_dlist_t g_tag_hash[TAG_HASH_SIZE];
char g_pool[POOL_SIZE];
struct ramfs_mgr *g_mgr_pool = NULL;
unsigned char *g_data_pool = NULL;

static void hexdump(unsigned char *buf, int len, const char *str, ...)
{
    int i, size;
    unsigned short *ptr;

    va_list args;
    va_start(args, str);
    vprintf(str, args);
    va_end(args);

    ptr = (unsigned short *)buf;
    size = (len + 2) / 2;
    for (i = 0; i < size; ++i) {
        if (i % 8 == 0) {
            printf("\n%p:", ptr + i);
        }
        printf(" %04x", ptr[i]);
    }
    printf("\n");
}

static short find_free_cluster(void)
{
    short i;

    for (i = 0; i < CLUSTER_NUM; ++i) {
        if (g_mgr_pool[i].first_data == 0xffff) {
            return i;
        }
    }
    return -1;
}

static unsigned char get_hash(unsigned char *ptr, int len)
{
    int i;
    unsigned char hash = 0;

    for (i = 0; i < len; ++i) {
        hash ^= ptr[i];
    }
    return hash;
}

static void store_tag(struct ramfs_tag *tag)
{
    unsigned char hash = get_hash((unsigned char *)&tag->tag, sizeof(tag->tag));
    log("tag %#x hash %#x", tag->tag, hash);

    sys_dlist_append(&g_tag_hash[hash & TAG_HASH_MASK], &tag->node);
}

static struct ramfs_tag *find_tag(unsigned short tag)
{
    struct ramfs_tag *p_tag = NULL;
    sys_dnode_t *node = NULL;
    unsigned char hash;
    short cluster;

    if ((tag & TAG_BIT) != TAG_BIT) {
        return NULL;
    }

    hash = get_hash((unsigned char *)&tag, sizeof(tag));
    log("tag %#x hash %#x", tag, hash);

    SYS_DLIST_FOR_EACH_NODE(&g_tag_hash[hash & TAG_HASH_MASK], node) {
        p_tag = (struct ramfs_tag *)node;
        if (p_tag->tag == tag) {
            return p_tag;
        }
    }

    return NULL;
}

int init(void)
{
    int i;

    for (i = 0; i < TAG_HASH_SIZE; ++i) {
        sys_dlist_init(&g_tag_hash[i]);
    }

    memset(g_pool, 0xff, sizeof(g_pool));

    g_mgr_pool  = (struct ramfs_mgr *)&g_pool[0];
    g_data_pool = &g_pool[CLUSTER_MGR_TOTAL_SIZE];

    return 0;
}

unsigned short create(unsigned short tag)
{
    struct ramfs_tag *p_tag;
    unsigned char hash;
    short cluster = find_free_cluster();
    log("cluster %d", cluster);

    g_mgr_pool[cluster].first_data = 0;

    p_tag = (struct ramfs_tag *)&g_data_pool[cluster * CLUSTER_DATA_SIZE];

    p_tag->sz = 0;
    p_tag->tag = tag | TAG_BIT;

    store_tag(p_tag);

    return p_tag->tag;
}

int write(unsigned short tag, const unsigned char *data, int len)
{
    struct ramfs_tag *p_tag = NULL;
    short cluster = -1, n_cluster = -1;
    unsigned short w_sz = 0, sz = 0;

    p_tag = find_tag(tag);
    log("p_tag %p", p_tag);
    if (p_tag == NULL) {
        return -1;
    }
    cluster = ((unsigned char *)p_tag - g_data_pool) / CLUSTER_DATA_SIZE;
    log("find tag %#x cluster %#x", p_tag->tag, cluster);

    g_mgr_pool[cluster].first_data = find_free_cluster();
    log("[%#x] %#x", cluster, g_mgr_pool[cluster].first_data);
    if (g_mgr_pool[cluster].first_data == -1) {
        g_mgr_pool[cluster].first_data = 0;
        return -1;
    }
    cluster = g_mgr_pool[cluster].first_data;

    do {
        // do copy
        sz = min(len, CLUSTER_DATA_SIZE);
        memcpy(&g_data_pool[cluster * CLUSTER_DATA_SIZE], &data[w_sz], sz);
        w_sz += sz;
        len  -= sz;

        if (len == 0) {
            g_mgr_pool[cluster].next_data = 0;
            log("[%#x] %#x", cluster, g_mgr_pool[cluster].next_data);
            break;
        } else {
            n_cluster = find_free_cluster();
            if (n_cluster == -1) {
                break;
            }
            g_mgr_pool[cluster].next_data = n_cluster;
            log("[%#x] %#x", cluster, g_mgr_pool[cluster].next_data);
            cluster = n_cluster;
        }
    } while (1);

    p_tag->sz = w_sz;

    return w_sz;
}

int read(unsigned short tag, unsigned char *data, int len)
{
    struct ramfs_tag *p_tag = NULL;
    short cluster = -1, n_cluster = -1;
    unsigned short r_sz = 0, sz = 0;

    p_tag = find_tag(tag);
    log("p_tag %p", p_tag);
    if (p_tag == NULL) {
        return -1;
    }

    len = min(p_tag->sz, len);

    cluster = ((unsigned char *)p_tag - g_data_pool) / CLUSTER_DATA_SIZE;
    log("find tag %#x cluster %#x", p_tag->tag, cluster);

    log("[%#x] %#x", cluster, g_mgr_pool[cluster].first_data);
    cluster = g_mgr_pool[cluster].first_data;
    if (cluster < 0) {
        return -1;
    } else if (cluster == 0) {
        return 0;
    }

    do {
        sz = min(len, CLUSTER_DATA_SIZE);
        memcpy(&data[r_sz], &g_data_pool[cluster * CLUSTER_DATA_SIZE], sz);
        r_sz += sz;
        len  -= sz;

        if (len == 0) {
            break;
        }

        log("[%#x] %#x", cluster, g_mgr_pool[cluster].next_data);
        cluster = g_mgr_pool[cluster].next_data;
        if (cluster < 0) {
            break;
        }
    } while (1);

    return r_sz;
}

int main(int argc, char const *argv[])
{
    int ret;
    unsigned short tag;
    char r_buf[0x20];
    const char w_buf[] = "Hello, this is tiny ramfs.";

    log("sizeof(int) %ld sizeof(struct ramfs_mgr) %ld", sizeof(int), sizeof(struct ramfs_mgr));
    log("POOL_SIZE %#x CLUSTER_MGR_SIZE %#lx CLUSTER_DATA_SIZE %#x CLUSTER_NUM %#lx CLUSTER_MGR_TOTAL_SIZE %#lx CLUSTER_DATA_TOTAL_SIZE %#lx",
        POOL_SIZE, CLUSTER_MGR_SIZE, CLUSTER_DATA_SIZE, CLUSTER_NUM, CLUSTER_MGR_TOTAL_SIZE, CLUSTER_DATA_TOTAL_SIZE);

    init();

    tag = create(0x1102);
    log("tag %#x", tag);

    ret = write(tag, w_buf, strlen(w_buf));
    log("write %#lx ret %#x", strlen(w_buf), ret);

    ret = read(tag, r_buf, sizeof(r_buf));
    log("read ret %#x %.*s", ret, ret, r_buf);
    // hexdump(r_buf, ret, "r_buf");

    tag = create(0x1103);
    log("tag %#x", tag);

    ret = write(tag, w_buf, strlen(w_buf));
    log("write %#lx ret %#x", strlen(w_buf), ret);

    ret = read(tag, r_buf, sizeof(r_buf));
    log("read ret %#x %.*s", ret, ret, r_buf);
    // hexdump(r_buf, ret, "r_buf");

    hexdump(g_pool, sizeof(g_pool)>>4, "init done");

    /* code */
    return 0;
}
